#include "wtlpch.h"

#include "HttpStreamHandler.h"

// Sorry for this kind of init static map
std::pair<short, string> pairTempReq[] = 
{
	std::make_pair(CHttpStreamHandler::ACCEPTENCODING,		"Accept-Encoding"),
	std::make_pair(CHttpStreamHandler::CONNECTION,			"Connection"),
	std::make_pair(CHttpStreamHandler::COOKIE,				"Cookie"),
	std::make_pair(CHttpStreamHandler::HOST,				"Host"),
	std::make_pair(CHttpStreamHandler::PRAGMA,				"Pragma"),
	std::make_pair(CHttpStreamHandler::REFERER,				"Referer"),
	std::make_pair(CHttpStreamHandler::USERAGENT,			"User-Agent")
};

std::pair<short, string> pairTempResp[] = 
{
	std::make_pair(CHttpStreamHandler::CONTENTENCODING,		"Content-Encoding"),
	std::make_pair(CHttpStreamHandler::CONTENTLENGTH,		"Content-Length"),
	std::make_pair(CHttpStreamHandler::CONTENTDISPOSITION,	"Content-Disposition"),
	std::make_pair(CHttpStreamHandler::CONTENTMD5,			"Content-MD5"),
	std::make_pair(CHttpStreamHandler::CONTENTTYPE,			"Content-Type"),
	std::make_pair(CHttpStreamHandler::ETAG,				"ETag"),
	std::make_pair(CHttpStreamHandler::LASTMODIFIED,		"Last-Modified"),
	std::make_pair(CHttpStreamHandler::LOCATION,			"Location"),
	std::make_pair(CHttpStreamHandler::RETRYAFTER,			"Retry-After"),
	std::make_pair(CHttpStreamHandler::SETCOOKIE,			"Set-Cookie"),
	std::make_pair(CHttpStreamHandler::TRANSFERENCODING,	"Transfer-Encoding"),
	std::make_pair(CHttpStreamHandler::CONTENTRANGE,		"Content-Range"),

	std::make_pair(CHttpStreamHandler::FILESTATUS,			"213"),		//Normally with SIZE command to tell the file size
	std::make_pair(CHttpStreamHandler::ERRORLOGIN,			"530"),		//Login Error, username or password, or the server limit the login threads and forbiden several cons at one time
	std::make_pair(CHttpStreamHandler::FILENOEXIST,			"550"),		//When we use SIZE, RETR, SIZE, CWD to retire a file or directory, this error means that file not exist
	std::make_pair(CHttpStreamHandler::MOREINFO,			"350"),		//When we try the REST commmand to test resumeable ftp server, this code will return, in addition check STOR OR RETR
	std::make_pair(CHttpStreamHandler::READYTRANSFER,		"150")		//The ftp server is ready to open transfer
};

map<short, string> CHttpStreamHandler::REQ_HEADER(pairTempReq, pairTempReq + sizeof pairTempReq / sizeof pairTempReq[0]);
map<short, string> CHttpStreamHandler::RESP_HEADER(pairTempResp, pairTempResp + sizeof pairTempResp / sizeof pairTempResp[0]);


UINT CHttpStreamHandler::GetHeaderContentLength(vector<string>* pvecstrHeaders)
{
	UINT uiContLength = 0;

	//If this is a chunked transfer, No conten-length will be given
	//We assume the size is zero
	if(IsChunkedTransfer(pvecstrHeaders))
	{
		return uiContLength;
	}

	//We search serveral time for the case of redirection, we only take the last 
	for(vector<string>::iterator it = pvecstrHeaders->begin(); it !=  pvecstrHeaders->end(); ++it)
	{
		//We find that one
		if((*it).find(RESP_HEADER[CONTENTLENGTH]) == 0)
		{
			char pcDigitals[10];
			short sCurPos = 0;
			memset(pcDigitals, 0, 10);
			
			for(const char* pcPos = (*it).c_str(); *pcPos != '\0'; pcPos++)
			{
				if(*pcPos >= '0' && *pcPos <= '9')
				{
					pcDigitals[sCurPos] = *pcPos;
					sCurPos++;
				}
			}

			uiContLength = atol(pcDigitals);
		}
	}

	return uiContLength;
}

bool CHttpStreamHandler::IsChunkedTransfer(vector<string>* pvecstrHeaders)
{
	//chunked transfer rarely used for redirections, so we just do a one time check
	for(vector<string>::iterator it = pvecstrHeaders->begin(); it !=  pvecstrHeaders->end(); ++it)
	{
		//We find that one
		if((*it).find(RESP_HEADER[TRANSFERENCODING]) == 0)
		{
			//find gzip, so it's gzip
			if((*it).find("chunked") != string::npos)
				return true;
			else
				return false;
		}
	}

	//If do not find transfer-encoding, we assume it's not chunked
	return false;
}

bool CHttpStreamHandler::IsGzipContent(vector<string>* pvecstrHeaders)
{
	//Gzip content rarely used for redirections, so we just do a one time check
	for(vector<string>::iterator it = pvecstrHeaders->begin(); it !=  pvecstrHeaders->end(); ++it)
	{
		//We find that one
		if((*it).find(RESP_HEADER[CONTENTENCODING]) == 0)
		{
			//find gzip, so it's gzip
			if((*it).find("gzip") != string::npos)
				return true;
			else
				return false;
		}
	}

	//else return false, we find't gzip
	return false;
}


void CHttpStreamHandler::RemoveLineBreak(LPWSTR lpwstrContent)
{
	for(int i=0; lpwstrContent[i] != L'\0'; i++)
	{
		if(lpwstrContent[i] == L'\r' || lpwstrContent[i] == L'\n')
		{
			lpwstrContent[i] = L' ';
		}
	}	
}

void CHttpStreamHandler::RemoveLineBreakA(LPSTR lpcstrContent)
{
	for(int i=0; lpcstrContent[i] != '\0'; i++)
	{
		if(lpcstrContent[i] == '\r' || lpcstrContent[i] == '\n')
		{
			lpcstrContent[i] = ' ';
		}
	}	
}

LRESULT CHttpStreamHandler::DecodeGzipStream(CHAR* pcInStream, UINT uiCompSize, CHAR* pcOutStream, bool isBigBuffer)
{
	//Init the zlib
    z_stream strm;
	strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;

	strm.avail_in = 0;
	strm.next_in = Z_NULL;

	//Init for gzip decompress
    int nRet = inflateInit2(&strm, 16+MAX_WBITS);

	//Init Error
    if (nRet != Z_OK)
        return S_FALSE;

	//Some Chunked content do not provide a size
	if(uiCompSize == 0)
	{
		uiCompSize = (isBigBuffer ? BUFFER_GZ_HTTP_STREAM : BUFFER_GZ_HTTP_STREAM_S);
	}

	strm.avail_in = uiCompSize;
	strm.next_in = (Bytef*)pcInStream;

	//Do the decompress
	do {
		strm.avail_out = ( isBigBuffer ? BUFFER_DEC_HTTP_STREAM : BUFFER_DEC_HTTP_STREAM_S ); 
		strm.next_out = (Bytef*)pcOutStream;

		//We assume enough place
		nRet = inflate(&strm, Z_FINISH);

		ATLASSERT(nRet != Z_STREAM_ERROR);

		switch (nRet) 
		{  
		case Z_NEED_DICT:  
			nRet = Z_DATA_ERROR; 
		case Z_DATA_ERROR:
		case Z_MEM_ERROR:  
			inflateEnd(&strm);  
			return S_FALSE;  
		} 

	} while (strm.avail_out == 0);

	//Safe End
	inflateEnd(&strm);

	return (nRet == Z_STREAM_END ? S_OK : S_FALSE);	
}

int CHttpStreamHandler::JSTwoLayerSearch(vector<CHttpStreamHandler::JSItem>& vecJSItems, WCHAR* UContentBuffer, const WCHAR* pwcOuterRegx, const WCHAR* pwcBlockRegx)
{
	CAtlRegExp<> reOuterRx;
	CAtlRegExp<> reBlockRx;

	REParseError peOuter = reOuterRx.Parse(pwcOuterRegx);
	REParseError peInner = reBlockRx.Parse(pwcBlockRegx);

	if ((peOuter != REPARSE_ERROR_OK) || (peInner != REPARSE_ERROR_OK))
	{
		// Unexpected error.
		return 0;
	}

	CAtlREMatchContext<> mcOuter;
	const CAtlRegExp<>::RECHAR* pcOuterPos = UContentBuffer;

	while(reOuterRx.Match(pcOuterPos, &mcOuter, &pcOuterPos))
	{
		for (UINT nOuterGroupIndex = 0; nOuterGroupIndex < mcOuter.m_uNumGroups; ++nOuterGroupIndex)
		{
			const CAtlREMatchContext<>::RECHAR* szOuterStart = 0;
			const CAtlREMatchContext<>::RECHAR* szOuterEnd = 0;
			mcOuter.GetMatch(nOuterGroupIndex, &szOuterStart, &szOuterEnd);

			WCHAR wcLastBackup = *szOuterEnd;
			CAtlRegExp<>::RECHAR* szTempLast = const_cast<CAtlRegExp<>::RECHAR*>(szOuterEnd);
			*szTempLast = L'\0';

			CAtlREMatchContext<> mcBlock;
			const CAtlRegExp<>::RECHAR* pcBlockPos = szOuterStart;
			while(reBlockRx.Match(pcBlockPos, &mcBlock, &pcBlockPos))
			{

				JSItem jsItem;
				for (UINT nBlockGroupIndex = 0; nBlockGroupIndex < mcBlock.m_uNumGroups; ++nBlockGroupIndex)
				{
					const CAtlREMatchContext<>::RECHAR* szBlockStart = 0;
					const CAtlREMatchContext<>::RECHAR* szBlockEnd = 0;
					mcBlock.GetMatch(nBlockGroupIndex, &szBlockStart, &szBlockEnd);

					WCHAR wcItemLast = *szBlockEnd;
					CAtlRegExp<>::RECHAR* szItemLast = const_cast<CAtlRegExp<>::RECHAR*>(szBlockEnd);
					*szItemLast = L'\0';

					if(nBlockGroupIndex == JSItem::JSITEMID)
						jsItem.wstrID = szBlockStart;
					else if(nBlockGroupIndex == JSItem::JSFILEKB)
						jsItem.uiFileSizeKB = wcstol(szBlockStart, NULL, 10);
					else if(nBlockGroupIndex == JSItem::JSFILENAME)
						jsItem.wstrFileName = szBlockStart;

					*szItemLast = wcItemLast;
				}

				vecJSItems.push_back(jsItem);

			}

			*szTempLast = wcLastBackup;
		}
	}

	return vecJSItems.size();
}

int CHttpStreamHandler::JSGetSecIP(string& strURL, map<string, string>& mapSecIp, WCHAR* UContentBuffer, const WCHAR* pwcSecIpRegx)
{
	CAtlRegExp<> reSecIpRx;

	REParseError peSecIp = reSecIpRx.Parse(pwcSecIpRegx);

	if(peSecIp != REPARSE_ERROR_OK)
	{
		// Unexpected error.
		return 0;
	}

	CAtlREMatchContext<> mcContext;
	const CAtlRegExp<>::RECHAR* pcPos = UContentBuffer;

	//Cacl how many we found in one search
	UINT uiFound = 0;
	if(reSecIpRx.Match(pcPos, &mcContext, &pcPos))
	{
		for(UINT nOuterGroupIndex = 0; nOuterGroupIndex < mcContext.m_uNumGroups; ++nOuterGroupIndex)
		{
			const CAtlREMatchContext<>::RECHAR* szStart = 0;
			const CAtlREMatchContext<>::RECHAR* szEnd = 0;
			mcContext.GetMatch(nOuterGroupIndex, &szStart, &szEnd);

			//We Assume all ansi, and no mbcs
			UINT uiBufferSize = szEnd- szStart + 1;
			WCHAR* lpwstrTempHolder = new WCHAR[uiBufferSize];
			CHAR* lpstrSecIpHolder = new CHAR[uiBufferSize*2];		//No neccessary, but overflow is no good
			::wmemset(lpwstrTempHolder, 0, uiBufferSize);
			::memset(lpstrSecIpHolder, 0, uiBufferSize*2);
	
			//Copy the matched string, szEnd and szStart are pointer to RECHAR, so
			//the substraction is cacled by WCHAR
			::wmemcpy(lpwstrTempHolder, szStart, szEnd-szStart);

			//Convert Unicode to ansi, it's safe to asume all char are ansi, cause the url regex paterns make so
			::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, lpwstrTempHolder, -1, lpstrSecIpHolder, uiBufferSize*2, NULL, NULL);
			mapSecIp[strURL] = string(lpstrSecIpHolder);

			uiFound++;
			
			//Clear the buffer
			delete lpwstrTempHolder;
			delete lpstrSecIpHolder;
		}
	}
	else		//If we did not found one, we just set a default secip
	{
		mapSecIp[strURL] = string("FFFF");
	}

	return uiFound;
}

bool CHttpStreamHandler::IsCaptchaNeeded(LPWSTR lpwstrContent)
{
	CAtlRegExp<> reIsCaptchaRx;

	REParseError peCaptcha = reIsCaptchaRx.Parse(_ConfigApp.JSSettings.wstrJSCaptchaKeyWord.c_str());

	if(peCaptcha != REPARSE_ERROR_OK)
	{
		// Unexpected error.
		return false;
	}

	CAtlREMatchContext<> mcContext;
	const CAtlRegExp<>::RECHAR* pcPos = lpwstrContent;

	//Is this content need captcha
	bool bIsCaptcha = false;
	if(reIsCaptchaRx.Match(pcPos, &mcContext, &pcPos))
	{
		//We Only Care about whether there need a captcha
		bIsCaptcha = true;
	}

	return bIsCaptcha;
}

bool CHttpStreamHandler::GetCaptchaLink(LPWSTR lpwstrContent, string& strCaptchaLink)
{
	CAtlRegExp<> reIsCaptchaLinkRx;

	REParseError peCaptcha = reIsCaptchaLinkRx.Parse(_ConfigApp.JSSettings.wstrJSCaptchaLink.c_str());

	if(peCaptcha != REPARSE_ERROR_OK)
	{
		// Unexpected error.
		return false;
	}

	CAtlREMatchContext<> mcContext;
	const CAtlRegExp<>::RECHAR* pcPos = lpwstrContent;

	//Is this content need captcha
	bool bIsGetCaptcha = false;
	if(reIsCaptchaLinkRx.Match(pcPos, &mcContext, &pcPos))
	{
		//We Find a captcha link
		bIsGetCaptcha = true;

		//Get the captcha link, there only one group, so the for loop is not really needed
		for(UINT nOuterGroupIndex = 0; nOuterGroupIndex < mcContext.m_uNumGroups; ++nOuterGroupIndex)
		{
			const CAtlREMatchContext<>::RECHAR* szStart = 0;
			const CAtlREMatchContext<>::RECHAR* szEnd = 0;
			mcContext.GetMatch(nOuterGroupIndex, &szStart, &szEnd);

			//We Assume URL to ansi, and no mbcs
			UINT uiBufferSize = szEnd- szStart + 1;
			WCHAR* lpwstrTempHolder = new WCHAR[uiBufferSize];
			CHAR* lpstrLinkHolder = new CHAR[uiBufferSize*2];		//No neccessary, but overflow is no good
			::wmemset(lpwstrTempHolder, 0, uiBufferSize);
			::memset(lpstrLinkHolder, 0, uiBufferSize*2);
	
			//Copy the matched string, szEnd and szStart are pointer to RECHAR, so
			//the substraction is cacled by WCHAR
			::wmemcpy(lpwstrTempHolder, szStart, szEnd-szStart);

			//Convert Unicode to ansi, it's safe to asume all char are ansi, cause the url regex paterns make so
			::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, lpwstrTempHolder, -1, lpstrLinkHolder, uiBufferSize*2, NULL, NULL);
			strCaptchaLink = lpstrLinkHolder;

			//Clear the buffer
			delete lpwstrTempHolder;
			delete lpstrLinkHolder;
		}

	}

	return bIsGetCaptcha;
}

bool CHttpStreamHandler::GetDownLoadLinkFromJSPage(LPWSTR lpwstrContent, string& strFileLink, string& strDownLoadLink)
{
	CAtlRegExp<> reLinkRx;
	string strDownRouter;

	REParseError peLink = reLinkRx.Parse(_ConfigApp.JSSettings.wstrJSPageRouterLink.c_str());

	if(peLink != REPARSE_ERROR_OK)
	{
		// Unexpected error.
		return false;
	}

	CAtlREMatchContext<> mcContext;
	const CAtlRegExp<>::RECHAR* pcPos = lpwstrContent;

	//Sniffer the Router Link
	if(reLinkRx.Match(pcPos, &mcContext, &pcPos))
	{
		//Get the captcha link, there only one group, so the for loop is not really needed
		for(UINT nOuterGroupIndex = 0; nOuterGroupIndex < mcContext.m_uNumGroups; ++nOuterGroupIndex)
		{
			const CAtlREMatchContext<>::RECHAR* szStart = 0;
			const CAtlREMatchContext<>::RECHAR* szEnd = 0;
			mcContext.GetMatch(nOuterGroupIndex, &szStart, &szEnd);

			//We Assume URL to ansi, and no mbcs
			UINT uiBufferSize = szEnd- szStart + 1;
			WCHAR* lpwstrTempHolder = new WCHAR[uiBufferSize];
			CHAR* lpstrLinkHolder = new CHAR[uiBufferSize*2];		//No neccessary, but overflow is no good
			::wmemset(lpwstrTempHolder, 0, uiBufferSize);
			::memset(lpstrLinkHolder, 0, uiBufferSize*2);
	
			//Copy the matched string, szEnd and szStart are pointer to RECHAR, so
			//the substraction is cacled by WCHAR
			::wmemcpy(lpwstrTempHolder, szStart, szEnd-szStart);

			//Convert Unicode to ansi, it's safe to asume all char are ansi, cause the url regex paterns make so
			::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, lpwstrTempHolder, -1, lpstrLinkHolder, uiBufferSize*2, NULL, NULL);
			strDownRouter = lpstrLinkHolder;

			//Clear the buffer
			delete lpwstrTempHolder;
			delete lpstrLinkHolder;
		}
	}
	else
	{
		//Got Fail if we donot get the link
		return false;
	}

	//Sniffer the file link
	peLink = reLinkRx.Parse(_ConfigApp.JSSettings.wstrJSPageFileLink.c_str());

	if(peLink != REPARSE_ERROR_OK)
	{
		// Unexpected error.
		return false;
	}
	
	//Reset the pos
	pcPos = lpwstrContent;
	if(reLinkRx.Match(pcPos, &mcContext, &pcPos))
	{
		//Get the captcha link, there only one group, so the for loop is not really needed
		for(UINT nOuterGroupIndex = 0; nOuterGroupIndex < mcContext.m_uNumGroups; ++nOuterGroupIndex)
		{
			const CAtlREMatchContext<>::RECHAR* szStart = 0;
			const CAtlREMatchContext<>::RECHAR* szEnd = 0;
			mcContext.GetMatch(nOuterGroupIndex, &szStart, &szEnd);

			//We Assume URL to ansi, and no mbcs
			UINT uiBufferSize = szEnd- szStart + 1;
			WCHAR* lpwstrTempHolder = new WCHAR[uiBufferSize];
			CHAR* lpstrLinkHolder = new CHAR[uiBufferSize*2];		//No neccessary, but overflow is no good
			::wmemset(lpwstrTempHolder, 0, uiBufferSize);
			::memset(lpstrLinkHolder, 0, uiBufferSize*2);
	
			//Copy the matched string, szEnd and szStart are pointer to RECHAR, so
			//the substraction is cacled by WCHAR
			::wmemcpy(lpwstrTempHolder, szStart, szEnd-szStart);

			//Convert Unicode to ansi, it's safe to asume all char are ansi, cause the url regex paterns make so
			::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, lpwstrTempHolder, -1, lpstrLinkHolder, uiBufferSize*2, NULL, NULL);
			strFileLink = lpstrLinkHolder;

			//Clear the buffer
			delete lpwstrTempHolder;
			delete lpstrLinkHolder;
		}
	}
	else	//Got Fail if we donot get the link
	{
		return false;
	}

	//DownLink need to be decode further more
	strDownLoadLink = strDownRouter;

	return true;
}

HANDLE CHttpStreamHandler::CreateTempFileWithExtA(LPSTR lpstrExt, string& strTempFileName)
{
	HANDLE hTempFile = NULL;

	//Set buffer for temp files
	CHAR lpszPathBuffer[MAX_PATH] = { 0 };
	CHAR lpszTempName[MAX_PATH] = { 0 };

	//Get the tempfileName
	::GetTempPathA(MAX_PATH, lpszPathBuffer);
	::GetTempFileNameA(lpszPathBuffer, "captcha", 0, lpszTempName);

	//Append the file ext
	strTempFileName = lpszTempName;
	strTempFileName += lpstrExt;

	//Rename the tempfile
	::MoveFileA(lpszTempName, strTempFileName.c_str());

	//Create the tempfiel
	hTempFile = ::CreateFileA(strTempFileName.c_str(),
									GENERIC_READ | GENERIC_WRITE,
									0,
									NULL,
									CREATE_ALWAYS,
									FILE_ATTRIBUTE_NORMAL,
									NULL);

	return hTempFile;
}